using System;
using System.Reflection;

namespace Com.ContentRendering.Api.DataEngine.Handler.Child
{
    /// <summary/>
    internal class IndexerChildHandler : AbstractChildHandler
    {
        /// <summary/>
        internal IndexerChildHandler(
            ReflectionCache reflectionCache,
            object data,
            string name)
            : base(
            reflectionCache,
            data,
            name) {}

        /// <summary/>
        internal override object Invoke()
        {
            Type type = base._data.GetType();

            // does the property not exist
            if (base._reflectionCache.IsIndexerCached(type))
            {
                return base._successor.Invoke();
            }

            // get the indexer name
            string indexerName = IndexerChildHandler.FetchIndexerName(type);

            // nothing to do if the indexer was found
            if (indexerName == null)
            {
                // add the object to the cache ... doesn't matter if the property info is null
                base._reflectionCache.AddIndexer(
                    type,
                    null);

                return base._successor.Invoke();
            }

            // fetch a property with the indexer that has a single string as an input 
            // parameter ... the output can be anything so don't check it
            PropertyInfo propertyInfo = type.GetProperty(
                indexerName, 
                new Type[] { typeof(string) });

            // add the object to the cache ... doesn't matter if the property info is null
            base._reflectionCache.AddIndexer(
                type,
                propertyInfo);

            // nothing to do if the indexer was not found
            if (propertyInfo == null)
            {
                return base._successor.Invoke();
            }            
                        
            // the dictionary class throws an exception if the indexer value is not valid ... this seems to be the only 
            // class that does this .. even the hashtable doesn't do this .. so lets fetch the checking method ...
            // theres no pint in checking in the type is in a dictionary type ... just fetch the method
            MethodInfo methodInfo = IndexerChildHandler.FetchContainsKeyMethod(type);

            // add the object to the cache ... doesn't matter if the property info is null
            base._reflectionCache.AddIndexerKeyMethod(
                type,
                methodInfo);

            // was a contains key method found
            if (methodInfo != null)
            {
                // execute the method and catch the result
                bool found = (bool)methodInfo.Invoke(
                    base._data,
                    new object[] { base._name });

                // return nothing if the key was not found
                if (!found)
                {
                    return null;
                }
            }

            // execute the property
            return propertyInfo.GetValue(
                this._data, 
                new object[] { base._name });
        }

        /// <summary/>
        private static string FetchIndexerName(Type type)
        {
            // fetch all of the custom attributes
            object[] attributes = type.GetCustomAttributes(true);

            // search the attributes for the default member
            foreach (Attribute attribute in attributes)
            {
                // make sure the type is correct
                if (!typeof(DefaultMemberAttribute).IsInstanceOfType(attribute))
                {
                    continue;
                }

                DefaultMemberAttribute defaultMemberAttribute = (DefaultMemberAttribute)attribute;

                // read the name of the attribute
                return defaultMemberAttribute.MemberName;
            }

            return null;
        }

        /// <summary/>
        private static MethodInfo FetchContainsKeyMethod(
            Type type)
        {
            MethodInfo[] methodInfos = type.GetMethods();

            // check each method on the class
            foreach (MethodInfo methodInfo in methodInfos)
            {
                // make sure the name matches
                if (methodInfo.Name != Constant.METHOD_CONTAINS_KEY)
                {
                    continue;
                }

                ParameterInfo returnParameterInfo = methodInfo.ReturnParameter;
                ParameterInfo[] parameterInfos = methodInfo.GetParameters();

                // if the return parameter is not a boolean then move on
                if (returnParameterInfo.ParameterType != typeof(bool))
                {
                    continue;
                }

                // make sure there is one and only one input parameter
                if (parameterInfos.Length != 1)
                {
                    continue;
                }

                // make sure the input parameter is a string
                if (parameterInfos[0].ParameterType != typeof(string))
                {
                    continue;
                }
                
                return methodInfo;
            }

            return null;
        }
    }
}
